qapi: Better error messages for bad expressions

The previous commit demonstrated that the generator overlooked some
fairly basic broken expressions:
- missing metataype
- metatype key has a non-string value
- unknown key in relation to the metatype
- conflicting metatype (this patch treats the second metatype as an
unknown key of the first key visited, which is not necessarily the
first key the user typed)

Add check_keys to cover these situations, and update testcases to
match.  A couple other tests (enum-missing-data, indented-expr) had
to change since the validation added here occurs so early.
Conversely, changes to ident-with-escape results show that we still
have problems where our handling of escape sequences differs from
true JSON, which will matter down the road if we allow arbitrary
default string values for optional parameters (but for now is not
too bad, as we currently can avoid unicode escaping as we don't
need to represent anything beyond C identifier material).

While valid .json files won't trigger any of these cases, we might
as well be nicer to developers that make a typo while trying to add
new QAPI code.

Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
Signed-off-by: Markus Armbruster <armbru@redhat.com>
This commit is contained in:
Eric Blake 2015-05-04 09:05:15 -06:00 committed by Markus Armbruster
parent 9050c65b71
commit 0545f6b887
24 changed files with 56 additions and 36 deletions

View File

@ -348,11 +348,6 @@ def check_alternate(expr, expr_info):
values = { 'MAX': '(automatic)' }
types_seen = {}
if expr.get('base') is not None:
raise QAPIExprError(expr_info,
"Alternate '%s' must not have a base"
% name)
# Check every branch
for (key, value) in members.items():
# Check for conflicts in the generated enum
@ -414,6 +409,26 @@ def check_exprs(schema):
elif expr.has_key('event'):
check_event(expr, info)
def check_keys(expr_elem, meta, required, optional=[]):
expr = expr_elem['expr']
info = expr_elem['info']
name = expr[meta]
if not isinstance(name, str):
raise QAPIExprError(info,
"'%s' key must have a string value" % meta)
required = required + [ meta ]
for (key, value) in expr.items():
if not key in required and not key in optional:
raise QAPIExprError(info,
"Unknown key '%s' in %s '%s'"
% (key, meta, name))
for key in required:
if not expr.has_key(key):
raise QAPIExprError(info,
"Key '%s' is missing from %s '%s'"
% (key, meta, name))
def parse_schema(input_file):
# First pass: read entire file into memory
try:
@ -425,15 +440,30 @@ def parse_schema(input_file):
exprs = []
try:
# Next pass: learn the types.
# Next pass: learn the types and check for valid expression keys. At
# this point, top-level 'include' has already been flattened.
for expr_elem in schema.exprs:
expr = expr_elem['expr']
if expr.has_key('enum'):
add_enum(expr['enum'], expr.get('data'))
check_keys(expr_elem, 'enum', ['data'])
add_enum(expr['enum'], expr['data'])
elif expr.has_key('union'):
check_keys(expr_elem, 'union', ['data'],
['base', 'discriminator'])
add_union(expr)
elif expr.has_key('alternate'):
check_keys(expr_elem, 'alternate', ['data'])
elif expr.has_key('type'):
check_keys(expr_elem, 'type', ['data'], ['base'])
add_struct(expr)
elif expr.has_key('command'):
check_keys(expr_elem, 'command', [],
['data', 'returns', 'gen', 'success-response'])
elif expr.has_key('event'):
check_keys(expr_elem, 'event', [], ['data'])
else:
raise QAPIExprError(expr_elem['info'],
"Expression is missing metatype")
exprs.append(expr)
# Try again for hidden UnionKind enum

View File

@ -1 +1 @@
tests/qapi-schema/alternate-base.json:4: Alternate 'Alt' must not have a base
tests/qapi-schema/alternate-base.json:4: Unknown key 'base' in alternate 'Alt'

View File

@ -0,0 +1 @@
tests/qapi-schema/bad-type-dict.json:2: 'command' key must have a string value

View File

@ -1 +1 @@
0
1

View File

@ -1,2 +1,2 @@
# FIXME: we should reject an expression with a metatype that is not a string
# we reject an expression with a metatype that is not a string
{ 'command': { } }

View File

@ -1,3 +0,0 @@
[OrderedDict([('command', OrderedDict())])]
[]
[]

View File

@ -0,0 +1 @@
tests/qapi-schema/double-type.json:2: Unknown key 'command' in type 'bar'

View File

@ -1 +1 @@
0
1

View File

@ -1,2 +1,2 @@
# FIXME: we should reject an expression with ambiguous metatype
# we reject an expression with ambiguous metatype
{ 'command': 'foo', 'type': 'bar', 'data': { } }

View File

@ -1,3 +0,0 @@
[OrderedDict([('command', 'foo'), ('type', 'bar'), ('data', OrderedDict())])]
[]
[OrderedDict([('command', 'foo'), ('type', 'bar'), ('data', OrderedDict())])]

View File

@ -1 +1 @@
tests/qapi-schema/enum-missing-data.json:2: Enum 'MyEnum' requires an array for 'data'
tests/qapi-schema/enum-missing-data.json:2: Key 'data' is missing from enum 'MyEnum'

View File

@ -0,0 +1 @@
tests/qapi-schema/ident-with-escape.json:3: Expression is missing metatype

View File

@ -1 +1 @@
0
1

View File

@ -1,3 +0,0 @@
[OrderedDict([('cu006fmmand', 'u0066u006fu006FA'), ('du0061ta', OrderedDict([('u0062u0061u00721', 'u0073u0074u0072')]))])]
[]
[]

View File

@ -1,2 +1,2 @@
{ 'id' : 'eins' }
{ 'id' : 'zwei' }
{ 'command' : 'eins' }
{ 'command' : 'zwei' }

View File

@ -1,3 +1,3 @@
[OrderedDict([('id', 'eins')]), OrderedDict([('id', 'zwei')])]
[OrderedDict([('command', 'eins')]), OrderedDict([('command', 'zwei')])]
[]
[]

View File

@ -0,0 +1 @@
tests/qapi-schema/missing-type.json:2: Expression is missing metatype

View File

@ -1 +1 @@
0
1

View File

@ -1,2 +1,2 @@
# FIXME: we should reject an expression with missing metatype
# we reject an expression with missing metatype
{ 'data': { } }

View File

@ -1,3 +0,0 @@
[OrderedDict([('data', OrderedDict())])]
[]
[]

View File

@ -0,0 +1 @@
tests/qapi-schema/unknown-expr-key.json:2: Unknown key 'bogus' in type 'bar'

View File

@ -1 +1 @@
0
1

View File

@ -1,2 +1,2 @@
# FIXME: we should reject an expression with unknown top-level keys
# we reject an expression with unknown top-level keys
{ 'type': 'bar', 'data': { 'string': 'str'}, 'bogus': { } }

View File

@ -1,3 +0,0 @@
[OrderedDict([('type', 'bar'), ('data', OrderedDict([('string', 'str')])), ('bogus', OrderedDict())])]
[]
[OrderedDict([('type', 'bar'), ('data', OrderedDict([('string', 'str')])), ('bogus', OrderedDict())])]