Merge tag 'pull-qapi-2023-04-26' of https://repo.or.cz/qemu/armbru into staging

QAPI patches patches for 2023-04-26

# -----BEGIN PGP SIGNATURE-----
#
# iQJGBAABCAAwFiEENUvIs9frKmtoZ05fOHC0AOuRhlMFAmRIvOkSHGFybWJydUBy
# ZWRoYXQuY29tAAoJEDhwtADrkYZTX3MQAIqrKQbOzQ81/cDZ7aeLOroDoGYf1Cs0
# 0NiEVlyoblWNzL3HraGgXiNRTP+zaG/TcFKza1nz8qjdkxWxBjdbfF5Lm6mQf5Zo
# tcHUjksmnUlPkLYSOyEjfY9SNvcS6g7djE/NF5lbJtzYGZScZpLarELR7oUvrcXR
# AEiw8N5FZXp+j6cTeWvrLzSqz9qBsFJUCGcGER0T/Mt5MlUwDexE1xe7g8oD5l+b
# s0jeQr1PTZm5k6ehajQtgbHvAkgH8xVTKqbB/U5iz4VhYriH+IPEOtfCFt6/1soz
# pVkYikJpazCvQMjqnWu9dE1onthgSsEIOV29kFU0Kr8ATZuJBQMuLVp4hSsbKANj
# BUVyL2/fUsIp7gd+KikXUOjKYajxek6Q2YLAPpL+1pBCTql/PBQ7td8CECdiv/9e
# Xh50q+BGvyEiyoyf4EEpaLXUZog605WHEaODj9uPtNHJP9x6Rqt93FUsdWUtt/k9
# hJ8RSKy8njr0vxGoJkj89m2XfCwtuX3VQ5IXvv/If4U5Y4+JhcLtiqW+Njh8fAM4
# ZwIrlUYG7inLUKFVcQ3sEGpaj611i5ixIxctUvEiggZX+fPeSFKYUr+Rq8WXM8gv
# suLXz7VF6H4Sw30lCvdQ4LSungbzlYAtQYpmdEQGoM8iasIi4PoDf0cTYBbMYHDX
# +pZvWC50cVtf
# =wLx6
# -----END PGP SIGNATURE-----
# gpg: Signature made Wed 26 Apr 2023 06:55:53 AM BST
# gpg:                using RSA key 354BC8B3D7EB2A6B68674E5F3870B400EB918653
# gpg:                issuer "armbru@redhat.com"
# gpg: Good signature from "Markus Armbruster <armbru@redhat.com>" [undefined]
# gpg:                 aka "Markus Armbruster <armbru@pond.sub.org>" [undefined]
# gpg: WARNING: This key is not certified with a trusted signature!
# gpg:          There is no indication that the signature belongs to the owner.
# Primary key fingerprint: 354B C8B3 D7EB 2A6B 6867  4E5F 3870 B400 EB91 8653

* tag 'pull-qapi-2023-04-26' of https://repo.or.cz/qemu/armbru:
  qapi: allow unions to contain further unions
  qapi: Improve specificity of type/member descriptions
  qapi: support updating expected test output via make
  qapi: Require boxed for conditional command and event arguments
  qapi: Fix code generated for optional conditional struct member
  tests/qapi-schema: Cover optional conditional struct member
  tests/qapi-schema: Clean up positive test for conditionals
  tests/qapi-schema: Rename a few conditionals
  tests/qapi-schema: Improve union discriminator coverage
  qapi: Fix to reject 'data': 'mumble' in struct
  qapi: Fix error message when type name or array is expected
  qapi: Simplify code a bit after previous commits
  qapi: Improve error message for unexpected array types
  qapi: Split up check_type()
  qapi: Clean up after removal of simple unions
  qapi/schema: Use super()
  qapi: Fix error message format regression

Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
This commit is contained in:
Richard Henderson
2023-04-26 07:23:32 +01:00
39 changed files with 383 additions and 103 deletions
+2 -3
View File
@@ -805,9 +805,8 @@ gets its generated code guarded like this::
... generated code ...
#endif /* defined(HAVE_BAR) && defined(CONFIG_FOO) */
Individual members of complex types, commands arguments, and
event-specific data can also be made conditional. This requires the
longhand form of MEMBER.
Individual members of complex types can also be made conditional.
This requires the longhand form of MEMBER.
Example: a struct type with unconditional member 'foo' and conditional
member 'bar' ::
+1
View File
@@ -66,6 +66,7 @@ def gen_call(name: str,
elif arg_type:
assert not arg_type.variants
for memb in arg_type.members:
assert not memb.ifcond.is_present()
if memb.need_has():
argstr += 'arg.has_%s, ' % c_name(memb.name)
argstr += 'arg.%s, ' % c_name(memb.name)
+62 -53
View File
@@ -333,62 +333,56 @@ def normalize_members(members: object) -> None:
members[key] = {'type': arg}
def check_type(value: Optional[object],
info: QAPISourceInfo,
source: str,
allow_array: bool = False,
allow_dict: Union[bool, str] = False) -> None:
"""
Normalize and validate the QAPI type of ``value``.
def check_type_name(value: Optional[object],
info: QAPISourceInfo, source: str) -> None:
if value is not None and not isinstance(value, str):
raise QAPISemError(info, "%s should be a type name" % source)
Python types of ``str`` or ``None`` are always allowed.
def check_type_name_or_array(value: Optional[object],
info: QAPISourceInfo, source: str) -> None:
if value is None or isinstance(value, str):
return
if not isinstance(value, list):
raise QAPISemError(info,
"%s should be a type name or array" % source)
if len(value) != 1 or not isinstance(value[0], str):
raise QAPISemError(info,
"%s: array type must contain single type name" %
source)
def check_type_implicit(value: Optional[object],
info: QAPISourceInfo, source: str,
parent_name: Optional[str]) -> None:
"""
Normalize and validate an optional implicit struct type.
Accept ``None`` or a ``dict`` defining an implicit struct type.
The latter is normalized in place.
:param value: The value to check.
:param info: QAPI schema source file information.
:param source: Error string describing this ``value``.
:param allow_array:
Allow a ``List[str]`` of length 1, which indicates an array of
the type named by the list element.
:param allow_dict:
Allow a dict. Its members can be struct type members or union
branches. When the value of ``allow_dict`` is in pragma
``member-name-exceptions``, the dict's keys may violate the
member naming rules. The dict members are normalized in place.
:param parent_name:
When the value of ``parent_name`` is in pragma
``member-name-exceptions``, an implicit struct type may
violate the member naming rules.
:raise QAPISemError: When ``value`` fails validation.
:return: None, ``value`` is normalized in-place as needed.
:return: None
"""
if value is None:
return
# Type name
if isinstance(value, str):
return
# Array type
if isinstance(value, list):
if not allow_array:
raise QAPISemError(info, "%s cannot be an array" % source)
if len(value) != 1 or not isinstance(value[0], str):
raise QAPISemError(info,
"%s: array type must contain single type name" %
source)
return
# Anonymous type
if not allow_dict:
raise QAPISemError(info, "%s should be a type name" % source)
if not isinstance(value, dict):
raise QAPISemError(info,
"%s should be an object or type name" % source)
permissive = False
if isinstance(allow_dict, str):
permissive = allow_dict in info.pragma.member_name_exceptions
permissive = parent_name in info.pragma.member_name_exceptions
# value is a dictionary, check that each member is okay
for (key, arg) in value.items():
key_source = "%s member '%s'" % (source, key)
if key.startswith('*'):
@@ -401,7 +395,16 @@ def check_type(value: Optional[object],
check_keys(arg, info, key_source, ['type'], ['if', 'features'])
check_if(arg, info, key_source)
check_features(arg.get('features'), info)
check_type(arg['type'], info, key_source, allow_array=True)
check_type_name_or_array(arg['type'], info, key_source)
def check_type_name_or_implicit(value: Optional[object],
info: QAPISourceInfo, source: str,
parent_name: Optional[str]) -> None:
if value is None or isinstance(value, str):
return
check_type_implicit(value, info, source, parent_name)
def check_features(features: Optional[object],
@@ -489,8 +492,8 @@ def check_struct(expr: QAPIExpression) -> None:
name = cast(str, expr['struct']) # Checked in check_exprs
members = expr['data']
check_type(members, expr.info, "'data'", allow_dict=name)
check_type(expr.get('base'), expr.info, "'base'")
check_type_implicit(members, expr.info, "'data'", name)
check_type_name(expr.get('base'), expr.info, "'base'")
def check_union(expr: QAPIExpression) -> None:
@@ -508,7 +511,7 @@ def check_union(expr: QAPIExpression) -> None:
members = expr['data']
info = expr.info
check_type(base, info, "'base'", allow_dict=name)
check_type_name_or_implicit(base, info, "'base'", name)
check_name_is_str(discriminator, info, "'discriminator'")
if not isinstance(members, dict):
@@ -518,7 +521,7 @@ def check_union(expr: QAPIExpression) -> None:
source = "'data' member '%s'" % key
check_keys(value, info, source, ['type'], ['if'])
check_if(value, info, source)
check_type(value['type'], info, source, allow_array=not base)
check_type_name(value['type'], info, source)
def check_alternate(expr: QAPIExpression) -> None:
@@ -544,7 +547,7 @@ def check_alternate(expr: QAPIExpression) -> None:
check_name_lower(key, info, source)
check_keys(value, info, source, ['type'], ['if'])
check_if(value, info, source)
check_type(value['type'], info, source, allow_array=True)
check_type_name_or_array(value['type'], info, source)
def check_command(expr: QAPIExpression) -> None:
@@ -560,10 +563,13 @@ def check_command(expr: QAPIExpression) -> None:
rets = expr.get('returns')
boxed = expr.get('boxed', False)
if boxed and args is None:
raise QAPISemError(expr.info, "'boxed': true requires 'data'")
check_type(args, expr.info, "'data'", allow_dict=not boxed)
check_type(rets, expr.info, "'returns'", allow_array=True)
if boxed:
if args is None:
raise QAPISemError(expr.info, "'boxed': true requires 'data'")
check_type_name(args, expr.info, "'data'")
else:
check_type_name_or_implicit(args, expr.info, "'data'", None)
check_type_name_or_array(rets, expr.info, "'returns'")
def check_event(expr: QAPIExpression) -> None:
@@ -578,9 +584,12 @@ def check_event(expr: QAPIExpression) -> None:
args = expr.get('data')
boxed = expr.get('boxed', False)
if boxed and args is None:
raise QAPISemError(expr.info, "'boxed': true requires 'data'")
check_type(args, expr.info, "'data'", allow_dict=not boxed)
if boxed:
if args is None:
raise QAPISemError(expr.info, "'boxed': true requires 'data'")
check_type_name(args, expr.info, "'data'")
else:
check_type_name_or_implicit(args, expr.info, "'data'", None)
def check_exprs(exprs: List[QAPIExpression]) -> List[QAPIExpression]:
+1
View File
@@ -119,6 +119,7 @@ def build_params(arg_type: Optional[QAPISchemaObjectType],
elif arg_type:
assert not arg_type.variants
for memb in arg_type.members:
assert not memb.ifcond.is_present()
ret += sep
sep = ', '
if memb.need_has():
+1 -1
View File
@@ -98,6 +98,6 @@ def main() -> int:
builtins=args.builtins,
gen_tracing=not args.suppress_tracing)
except QAPIError as err:
print(f"{sys.argv[0]}: {str(err)}", file=sys.stderr)
print(err, file=sys.stderr)
return 1
return 0
+25 -6
View File
@@ -259,7 +259,7 @@ class QAPISchemaType(QAPISchemaEntity):
return not self.c_type().endswith(POINTER_SUFFIX)
def check(self, schema):
QAPISchemaEntity.check(self, schema)
super().check(schema)
for feat in self.features:
if feat.is_special():
raise QAPISemError(
@@ -465,9 +465,10 @@ class QAPISchemaObjectType(QAPISchemaType):
# on behalf of info, which is not necessarily self.info
def check_clash(self, info, seen):
assert self._checked
assert not self.variants # not implemented
for m in self.members:
m.check_clash(info, seen)
if self.variants:
self.variants.check_clash(info, seen)
def connect_doc(self, doc=None):
super().connect_doc(doc)
@@ -486,6 +487,10 @@ class QAPISchemaObjectType(QAPISchemaType):
assert self.members is not None
return not self.members and not self.variants
def has_conditional_members(self):
assert self.members is not None
return any(m.ifcond.is_present() for m in self.members)
def c_name(self):
assert self.name != 'q_empty'
return super().c_name()
@@ -652,8 +657,7 @@ class QAPISchemaVariants:
self.info,
"branch '%s' is not a value of %s"
% (v.name, self.tag_member.type.describe()))
if (not isinstance(v.type, QAPISchemaObjectType)
or v.type.variants):
if not isinstance(v.type, QAPISchemaObjectType):
raise QAPISemError(
self.info,
"%s cannot use %s"
@@ -697,6 +701,7 @@ class QAPISchemaMember:
def describe(self, info):
role = self.role
meta = 'type'
defined_in = self.defined_in
assert defined_in
@@ -708,13 +713,17 @@ class QAPISchemaMember:
# Implicit type created for a command's dict 'data'
assert role == 'member'
role = 'parameter'
meta = 'command'
defined_in = defined_in[:-4]
elif defined_in.endswith('-base'):
# Implicit type created for a union's dict 'base'
role = 'base ' + role
defined_in = defined_in[:-5]
else:
assert False
elif defined_in != info.defn_name:
return "%s '%s' of type '%s'" % (role, self.name, defined_in)
if defined_in != info.defn_name:
return "%s '%s' of %s '%s'" % (role, self.name, meta, defined_in)
return "%s '%s'" % (role, self.name)
@@ -817,6 +826,11 @@ class QAPISchemaCommand(QAPISchemaEntity):
self.info,
"command's 'data' can take %s only with 'boxed': true"
% self.arg_type.describe())
self.arg_type.check(schema)
if self.arg_type.has_conditional_members() and not self.boxed:
raise QAPISemError(
self.info,
"conditional command arguments require 'boxed': true")
if self._ret_type_name:
self.ret_type = schema.resolve_type(
self._ret_type_name, self.info, "command's 'returns'")
@@ -872,6 +886,11 @@ class QAPISchemaEvent(QAPISchemaEntity):
self.info,
"event's 'data' can take %s only with 'boxed': true"
% self.arg_type.describe())
self.arg_type.check(schema)
if self.arg_type.has_conditional_members() and not self.boxed:
raise QAPISemError(
self.info,
"conditional event arguments require 'boxed': true")
def connect_doc(self, doc=None):
super().connect_doc(doc)
+2
View File
@@ -74,11 +74,13 @@ bool visit_type_%(c_name)s_members(Visitor *v, %(c_name)s *obj, Error **errp)
sep = ''
for memb in members:
if memb.optional and not memb.need_has():
ret += memb.ifcond.gen_if()
ret += mcgen('''
bool has_%(c_name)s = !!obj->%(c_name)s;
''',
c_name=c_name(memb.name))
sep = '\n'
ret += memb.ifcond.gen_endif()
ret += sep
if base:
+2
View File
@@ -0,0 +1,2 @@
args-if-implicit.json: In command 'test-if-cmd':
args-if-implicit.json:1: conditional command arguments require 'boxed': true
+4
View File
@@ -0,0 +1,4 @@
{ 'command': 'test-if-cmd',
'data': {
'foo': 'int',
'bar': { 'type': 'str', 'if': 'TEST_IF_CMD_ARG' } } }
+2
View File
@@ -0,0 +1,2 @@
args-if-unboxed.json: In command 'test-if-cmd':
args-if-unboxed.json:5: conditional command arguments require 'boxed': true
+6
View File
@@ -0,0 +1,6 @@
{ 'struct': 'TestIfCmdArgs',
'data': {
'foo': 'int',
'bar': { 'type': 'str', 'if': 'TEST_IF_CMD_ARG' } } }
{ 'command': 'test-if-cmd',
'data': 'TestIfCmdArgs' }
+1 -1
View File
@@ -1,2 +1,2 @@
bad-data.json: In command 'oops':
bad-data.json:2: 'data' cannot be an array
bad-data.json:2: 'data' should be an object or type name
@@ -0,0 +1,2 @@
tests/qapi-schema/event-args-if-unboxed.json: In event 'TEST_IF_EVENT':
tests/qapi-schema/event-args-if-unboxed.json:1: event's 'data' members may have 'if' conditions only with 'boxed': true
@@ -0,0 +1,4 @@
{ 'event': 'TEST_IF_EVENT',
'data': {
'foo': 'int',
'bar': { 'type': 'str', 'if': 'TEST_IF_CMD_ARG' } } }
+1 -1
View File
@@ -1,2 +1,2 @@
event-nest-struct.json: In event 'EVENT_A':
event-nest-struct.json:1: 'data' member 'a' should be a type name
event-nest-struct.json:1: 'data' member 'a' should be a type name or array
+5
View File
@@ -27,6 +27,8 @@ schemas = [
'args-bad-boxed.json',
'args-boxed-anon.json',
'args-boxed-string.json',
'args-if-implicit.json',
'args-if-unboxed.json',
'args-int.json',
'args-invalid.json',
'args-member-array-bad.json',
@@ -164,6 +166,7 @@ schemas = [
'struct-base-clash-deep.json',
'struct-base-clash.json',
'struct-data-invalid.json',
'struct-data-typename.json',
'struct-member-if-invalid.json',
'struct-member-invalid-dict.json',
'struct-member-invalid.json',
@@ -194,6 +197,8 @@ schemas = [
'union-invalid-data.json',
'union-invalid-discriminator.json',
'union-invalid-if-discriminator.json',
'union-invalid-union-subfield.json',
'union-invalid-union-subtype.json',
'union-no-base.json',
'union-optional-discriminator.json',
'union-string-discriminator.json',
+1 -1
View File
@@ -1,2 +1,2 @@
nested-struct-data.json: In command 'foo':
nested-struct-data.json:2: 'data' member 'a' should be a type name
nested-struct-data.json:2: 'data' member 'a' should be a type name or array

Some files were not shown because too many files have changed in this diff Show More