qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Qemu-devel] [PATCH v6 07/12] qapi: Detect collisions in C member na


From: Markus Armbruster
Subject: Re: [Qemu-devel] [PATCH v6 07/12] qapi: Detect collisions in C member names
Date: Fri, 02 Oct 2015 15:19:27 +0200
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/24.5 (gnu/linux)

Eric Blake <address@hidden> writes:

> Detect attempts to declare two object members that would result
> in the same C member name, by keying the 'seen' dictionary off
> of the C name rather than the qapi name.  Doing this was made
> easier by adding a member.c_name() helper function.

This gains protection against colliding C names.  It keeps protection
against colliding QMP names as long as QMP name collision implies C name
collision.  I think that's the case, but it's definitely worth spelling
out in the code, and possibly in the commit message.

> As this is the first error raised within the QAPISchema*.check()
> methods, we also have to pass 'info' through the call stack, and
> fix the overall 'try' to display errors detected during the
> check() phase.

Could also be a separate patch, if the parts are easier to review.

> This fixes a couple of previously-broken tests, and the

Just two.

> resulting error messages demonstrate the utility of the
> .describe() method added previously.  No change to generated
> code.
>
> Signed-off-by: Eric Blake <address@hidden>
>
> ---
> v6: rebase to earlier testsuite and info improvements
> ---
>  scripts/qapi.py                                | 46 
> ++++++++++++++++----------
>  tests/qapi-schema/args-name-clash.err          |  1 +
>  tests/qapi-schema/args-name-clash.exit         |  2 +-
>  tests/qapi-schema/args-name-clash.json         |  6 ++--
>  tests/qapi-schema/args-name-clash.out          |  6 ----
>  tests/qapi-schema/flat-union-clash-branch.err  |  1 +
>  tests/qapi-schema/flat-union-clash-branch.exit |  2 +-
>  tests/qapi-schema/flat-union-clash-branch.json |  9 ++---
>  tests/qapi-schema/flat-union-clash-branch.out  | 14 --------
>  9 files changed, 40 insertions(+), 47 deletions(-)
>
> diff --git a/scripts/qapi.py b/scripts/qapi.py
> index 880de94..1acf02b 100644
> --- a/scripts/qapi.py
> +++ b/scripts/qapi.py
> @@ -103,6 +103,7 @@ class QAPISchemaError(Exception):
>  class QAPIExprError(Exception):
>      def __init__(self, expr_info, msg):
>          Exception.__init__(self)
> +        assert expr_info
>          self.info = expr_info
>          self.msg = msg
>

Assertion should hold before the patch.  Could therefore be added in a
separate patch before this one.  Only if we have enough material for
a separate patch, or it makes this one materially easier to review.

> @@ -964,11 +965,12 @@ class QAPISchemaObjectType(QAPISchemaType):
>              members = []
>          seen = {}
>          for m in members:
> -            seen[m.name] = m
> +            assert m.c_name() not in seen
> +            seen[m.c_name()] = m

Assertion should hold before the patch.

>          for m in self.local_members:
> -            m.check(schema, members, seen)
> +            m.check(schema, self.info, members, seen)
>          if self.variants:
> -            self.variants.check(schema, members, seen)
> +            self.variants.check(schema, self.info, members, seen)
>          self.members = members
>
>      def is_implicit(self):
> @@ -1004,12 +1006,19 @@ class QAPISchemaObjectTypeMember(object):
>          self.optional = optional
>          self._owner = owner
>
> -    def check(self, schema, all_members, seen):
> -        assert self.name not in seen
> +    def check(self, schema, info, all_members, seen):
> +        if self.c_name() in seen:
> +            raise QAPIExprError(info,
> +                                "%s collides with %s"
> +                                % (self.describe(),
> +                                   seen[self.c_name()].describe()))
>          self.type = schema.lookup_type(self._type_name)
>          assert self.type
>          all_members.append(self)
> -        seen[self.name] = self
> +        seen[self.c_name()] = self
> +
> +    def c_name(self):
> +        return c_name(self.name)

Why wrap function c_name() in a method?  Why not simply call the
function?

It's method in QAPISchemaEntity only because this lets us add special
cases in a neat way.

>
>      def describe(self):
>          return "'%s' (member of %s)" % (self.name, self._owner)
> @@ -1028,23 +1037,24 @@ class QAPISchemaObjectTypeVariants(object):
>          self.tag_member = tag_member
>          self.variants = variants
>
> -    def check(self, schema, members, seen):
> +    def check(self, schema, info, members, seen):
>          if self.tag_name:
> -            self.tag_member = seen[self.tag_name]
> +            self.tag_member = seen[c_name(self.tag_name)]
> +            assert self.tag_name == self.tag_member.name

Assertion should hold before the patch, but it's kind of trivial then.

>          else:
> -            self.tag_member.check(schema, members, seen)
> +            self.tag_member.check(schema, info, members, seen)
>          assert isinstance(self.tag_member.type, QAPISchemaEnumType)
>          for v in self.variants:
>              vseen = dict(seen)
> -            v.check(schema, self.tag_member.type, vseen)
> +            v.check(schema, info, self.tag_member.type, vseen)
>
>
>  class QAPISchemaObjectTypeVariant(QAPISchemaObjectTypeMember):
>      def __init__(self, name, typ, owner):
>          QAPISchemaObjectTypeMember.__init__(self, name, typ, False, owner)
>
> -    def check(self, schema, tag_type, seen):
> -        QAPISchemaObjectTypeMember.check(self, schema, [], seen)
> +    def check(self, schema, info, tag_type, seen):
> +        QAPISchemaObjectTypeMember.check(self, schema, info, [], seen)
>          assert self.name in tag_type.values
>
>      def describe(self):
> @@ -1069,7 +1079,7 @@ class QAPISchemaAlternateType(QAPISchemaType):
>          self.variants = variants
>
>      def check(self, schema):
> -        self.variants.check(schema, [], {})
> +        self.variants.check(schema, self.info, [], {})
>
>      def json_type(self):
>          return 'value'
> @@ -1128,14 +1138,14 @@ class QAPISchema(object):
>      def __init__(self, fname):
>          try:
>              self.exprs = check_exprs(QAPISchemaParser(open(fname, 
> "r")).exprs)
> +            self._entity_dict = {}
> +            self._def_predefineds()
> +            QAPISchema.predefined_initialized = True
> +            self._def_exprs()
> +            self.check()
>          except (QAPISchemaError, QAPIExprError), err:
>              print >>sys.stderr, err
>              exit(1)
> -        self._entity_dict = {}
> -        self._def_predefineds()
> -        QAPISchema.predefined_initialized = True
> -        self._def_exprs()
> -        self.check()

Moving code into the try wholesale is okay because we catch only our own
exceptions.

>
>      def _def_entity(self, ent):
>          assert ent.name not in self._entity_dict
> diff --git a/tests/qapi-schema/args-name-clash.err 
> b/tests/qapi-schema/args-name-clash.err
> index e69de29..66f609c 100644
> --- a/tests/qapi-schema/args-name-clash.err
> +++ b/tests/qapi-schema/args-name-clash.err
> @@ -0,0 +1 @@
> +tests/qapi-schema/args-name-clash.json:5: 'a_b' (member of oops arguments) 
> collides with 'a-b' (member of oops arguments)
> diff --git a/tests/qapi-schema/args-name-clash.exit 
> b/tests/qapi-schema/args-name-clash.exit
> index 573541a..d00491f 100644
> --- a/tests/qapi-schema/args-name-clash.exit
> +++ b/tests/qapi-schema/args-name-clash.exit
> @@ -1 +1 @@
> -0
> +1
> diff --git a/tests/qapi-schema/args-name-clash.json 
> b/tests/qapi-schema/args-name-clash.json
> index 9e8f889..3fe4ea5 100644
> --- a/tests/qapi-schema/args-name-clash.json
> +++ b/tests/qapi-schema/args-name-clash.json
> @@ -1,5 +1,5 @@
>  # C member name collision
> -# FIXME - This parses, but fails to compile, because the C struct is given
> -# two 'a_b' members.  Either reject this at parse time, or munge the C names
> -# to avoid the collision.
> +# Reject members that clash when mapped to C names (we would have two 'a_b'
> +# members). It would also be possible to munge the C names to avoid the
> +# collision, but unlikely to be worth the effort.
>  { 'command': 'oops', 'data': { 'a-b': 'str', 'a_b': 'str' } }
> diff --git a/tests/qapi-schema/args-name-clash.out 
> b/tests/qapi-schema/args-name-clash.out
> index 9b2f6e4..e69de29 100644
> --- a/tests/qapi-schema/args-name-clash.out
> +++ b/tests/qapi-schema/args-name-clash.out
> @@ -1,6 +0,0 @@
> -object :empty
> -object :obj-oops-arg
> -    member a-b: str optional=False
> -    member a_b: str optional=False
> -command oops :obj-oops-arg -> None
> -   gen=True success_response=True
> diff --git a/tests/qapi-schema/flat-union-clash-branch.err 
> b/tests/qapi-schema/flat-union-clash-branch.err
> index e69de29..0190d79 100644
> --- a/tests/qapi-schema/flat-union-clash-branch.err
> +++ b/tests/qapi-schema/flat-union-clash-branch.err
> @@ -0,0 +1 @@
> +tests/qapi-schema/flat-union-clash-branch.json:15: 'c-d' (branch of 
> TestUnion) collides with 'c_d' (member of Base)
> diff --git a/tests/qapi-schema/flat-union-clash-branch.exit 
> b/tests/qapi-schema/flat-union-clash-branch.exit
> index 573541a..d00491f 100644
> --- a/tests/qapi-schema/flat-union-clash-branch.exit
> +++ b/tests/qapi-schema/flat-union-clash-branch.exit
> @@ -1 +1 @@
> -0
> +1
> diff --git a/tests/qapi-schema/flat-union-clash-branch.json 
> b/tests/qapi-schema/flat-union-clash-branch.json
> index e593336..a6c302f 100644
> --- a/tests/qapi-schema/flat-union-clash-branch.json
> +++ b/tests/qapi-schema/flat-union-clash-branch.json
> @@ -1,8 +1,9 @@
>  # Flat union branch name collision
> -# FIXME: this parses, but then fails to compile due to a duplicate 'c_d'
> -# (one from the base member, the other from the branch name).  We should
> -# either reject the collision at parse time, or munge the generated branch
> -# name to allow this to compile.
> +# Reject attempts to use a branch name that would clash with a non-variant
> +# member, when mapped to C names (here, we would have two 'c_d' members,
> +# one from the base member, the other from the branch name).
> +# TODO: We could munge the generated branch name to avoid the collision and
> +# allow this to compile.
>  { 'enum': 'TestEnum',
>    'data': [ 'base', 'c-d' ] }
>  { 'struct': 'Base',
> diff --git a/tests/qapi-schema/flat-union-clash-branch.out 
> b/tests/qapi-schema/flat-union-clash-branch.out
> index 8e0da73..e69de29 100644
> --- a/tests/qapi-schema/flat-union-clash-branch.out
> +++ b/tests/qapi-schema/flat-union-clash-branch.out
> @@ -1,14 +0,0 @@
> -object :empty
> -object Base
> -    member enum1: TestEnum optional=False
> -    member c_d: str optional=True
> -object Branch1
> -    member string: str optional=False
> -object Branch2
> -    member value: int optional=False
> -enum TestEnum ['base', 'c-d']
> -object TestUnion
> -    base Base
> -    tag enum1
> -    case base: Branch1
> -    case c-d: Branch2

I'm fine with not splitting this patch.  I'd be also fine with splitting
it up.  Your choice.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]