Previously, working with alternates required two lookup arrays
and some indirection: for type Foo, we created Foo_qtypes[]
which maps each qtype to a value of the generated FooKind enum,
then look up that value in FooKind_lookup[] like we do for other
union types.
This has a couple of subtle bugs. First, the generator was
creating a call with a parameter '(int *) &(*obj)->type' where
type is an enum type; this is unsafe if the compiler chooses
to store the enum type in a different size than int, where
assigning through the wrong size pointer can corrupt data or
cause a SIGBUS.
Related bug, not not fixed in this patch: qapi-visit.py's
gen_visit_enum() generates a cast of its enum * argument to
int *. Marked FIXME.
Second, since the values of the FooKind enum start at zero, all
entries of the Foo_qtypes[] array that were not explicitly
initialized will map to the same branch of the union as the
first member of the alternate, rather than triggering a desired
failure in visit_get_next_type(). Fortunately, the bug seldom
bites; the very next thing the input visitor does is try to
parse the incoming JSON with the wrong parser, which normally
fails; the output visitor is not used with a C struct in that
state, and the dealloc visitor has nothing to clean up (so
there is no leak).
However, the second bug IS observable in one case: parsing an
integer causes unusual behavior in an alternate that contains
at least a 'number' member but no 'int' member, because the
'number' parser accepts QTYPE_QINT in addition to the expected
QTYPE_QFLOAT (that is, since 'int' is not a member, the type
QTYPE_QINT accidentally maps to FooKind 0; if this enum value
is the 'number' branch the integer parses successfully, but if
the 'number' branch is not first, some other branch tries to
parse the integer and rejects it). A later patch will worry
about fixing alternates to always parse all inputs that a
non-alternate 'number' would accept, for now this is still
marked FIXME in the updated test-qmp-input-visitor.c, to
merely point out that new undesired behavior of 'ans' matches
the existing undesired behavior of 'asn'.
This patch fixes the default-initialization bug by deleting the
indirection, and modifying get_next_type() to directly assign a
QTypeCode parameter. This in turn fixes the type-casting bug,
as we are no longer casting a pointer to enum to a questionable
size. There is no longer a need to generate an implicit FooKind
enum associated with the alternate type (since the QMP wire
format never uses the stringized counterparts of the C union
member names). Since the updated visit_get_next_type() does not
know which qtypes are expected, the generated visitor is
modified to generate an error statement if an unexpected type is
encountered.
Callers now have to know the QTYPE_* mapping when looking at the
discriminator; but so far, only the testsuite was even using the
C struct of an alternate types. I considered the possibility of
keeping the internal enum FooKind, but initialized differently
than most generated arrays, as in:
typedef enum FooKind {
FOO_KIND_A = QTYPE_QDICT,
FOO_KIND_B = QTYPE_QINT,
} FooKind;
to create nicer aliases for knowing when to use foo->a or foo->b
when inspecting foo->type; but it turned out to add too much
complexity, especially without a client.
There is a user-visible side effect to this change, but I
consider it to be an improvement. Previously,
the invalid QMP command:
{"execute":"blockdev-add", "arguments":{"options":
{"driver":"raw", "id":"a", "file":true}}}
failed with:
{"error": {"class": "GenericError",
"desc": "Invalid parameter type for 'file', expected: QDict"}}
(visit_get_next_type() succeeded, and the error comes from the
visit_type_BlockdevOptions() expecting {}; there is no mention of
the fact that a string would also work). Now it fails with:
{"error": {"class": "GenericError",
"desc": "Invalid parameter type for 'file', expected: BlockdevRef"}}
(the error when the next type doesn't match any expected types for
the overall alternate).
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <1449033659-25497-5-git-send-email-eblake@redhat.com>
Signed-off-by: Markus Armbruster <armbru@redhat.com>
306 lines
8.3 KiB
Python
306 lines
8.3 KiB
Python
#
|
|
# QAPI types generator
|
|
#
|
|
# Copyright IBM, Corp. 2011
|
|
# Copyright (c) 2013-2015 Red Hat Inc.
|
|
#
|
|
# Authors:
|
|
# Anthony Liguori <aliguori@us.ibm.com>
|
|
# Markus Armbruster <armbru@redhat.com>
|
|
#
|
|
# This work is licensed under the terms of the GNU GPL, version 2.
|
|
# See the COPYING file in the top-level directory.
|
|
|
|
from qapi import *
|
|
|
|
|
|
def gen_fwd_object_or_array(name):
|
|
return mcgen('''
|
|
|
|
typedef struct %(c_name)s %(c_name)s;
|
|
''',
|
|
c_name=c_name(name))
|
|
|
|
|
|
def gen_array(name, element_type):
|
|
return mcgen('''
|
|
|
|
struct %(c_name)s {
|
|
union {
|
|
%(c_type)s value;
|
|
uint64_t padding;
|
|
};
|
|
%(c_name)s *next;
|
|
};
|
|
''',
|
|
c_name=c_name(name), c_type=element_type.c_type())
|
|
|
|
|
|
def gen_struct_fields(members):
|
|
ret = ''
|
|
for memb in members:
|
|
if memb.optional:
|
|
ret += mcgen('''
|
|
bool has_%(c_name)s;
|
|
''',
|
|
c_name=c_name(memb.name))
|
|
ret += mcgen('''
|
|
%(c_type)s %(c_name)s;
|
|
''',
|
|
c_type=memb.type.c_type(), c_name=c_name(memb.name))
|
|
return ret
|
|
|
|
|
|
def gen_object(name, base, members, variants):
|
|
ret = mcgen('''
|
|
|
|
struct %(c_name)s {
|
|
''',
|
|
c_name=c_name(name))
|
|
|
|
if base:
|
|
ret += mcgen('''
|
|
/* Members inherited from %(c_name)s: */
|
|
''',
|
|
c_name=base.c_name())
|
|
ret += gen_struct_fields(base.members)
|
|
ret += mcgen('''
|
|
/* Own members: */
|
|
''')
|
|
ret += gen_struct_fields(members)
|
|
|
|
if variants:
|
|
ret += gen_variants(variants)
|
|
|
|
# Make sure that all structs have at least one field; this avoids
|
|
# potential issues with attempting to malloc space for zero-length
|
|
# structs in C, and also incompatibility with C++ (where an empty
|
|
# struct is size 1).
|
|
if not (base and base.members) and not members and not variants:
|
|
ret += mcgen('''
|
|
char qapi_dummy_field_for_empty_struct;
|
|
''')
|
|
|
|
ret += mcgen('''
|
|
};
|
|
''')
|
|
|
|
return ret
|
|
|
|
|
|
def gen_upcast(name, base):
|
|
# C makes const-correctness ugly. We have to cast away const to let
|
|
# this function work for both const and non-const obj.
|
|
return mcgen('''
|
|
|
|
static inline %(base)s *qapi_%(c_name)s_base(const %(c_name)s *obj)
|
|
{
|
|
return (%(base)s *)obj;
|
|
}
|
|
''',
|
|
c_name=c_name(name), base=base.c_name())
|
|
|
|
|
|
def gen_variants(variants):
|
|
# FIXME: What purpose does data serve, besides preventing a union that
|
|
# has a branch named 'data'? We use it in qapi-visit.py to decide
|
|
# whether to bypass the switch statement if visiting the discriminator
|
|
# failed; but since we 0-initialize structs, and cannot tell what
|
|
# branch of the union is in use if the discriminator is invalid, there
|
|
# should not be any data leaks even without a data pointer. Or, if
|
|
# 'data' is merely added to guarantee we don't have an empty union,
|
|
# shouldn't we enforce that at .json parse time?
|
|
ret = mcgen('''
|
|
union { /* union tag is @%(c_name)s */
|
|
void *data;
|
|
''',
|
|
c_name=c_name(variants.tag_member.name))
|
|
|
|
for var in variants.variants:
|
|
# Ugly special case for simple union TODO get rid of it
|
|
typ = var.simple_union_type() or var.type
|
|
ret += mcgen('''
|
|
%(c_type)s %(c_name)s;
|
|
''',
|
|
c_type=typ.c_type(),
|
|
c_name=c_name(var.name))
|
|
|
|
ret += mcgen('''
|
|
} u;
|
|
''')
|
|
|
|
return ret
|
|
|
|
|
|
def gen_type_cleanup_decl(name):
|
|
ret = mcgen('''
|
|
|
|
void qapi_free_%(c_name)s(%(c_name)s *obj);
|
|
''',
|
|
c_name=c_name(name))
|
|
return ret
|
|
|
|
|
|
def gen_type_cleanup(name):
|
|
ret = mcgen('''
|
|
|
|
void qapi_free_%(c_name)s(%(c_name)s *obj)
|
|
{
|
|
QapiDeallocVisitor *qdv;
|
|
Visitor *v;
|
|
|
|
if (!obj) {
|
|
return;
|
|
}
|
|
|
|
qdv = qapi_dealloc_visitor_new();
|
|
v = qapi_dealloc_get_visitor(qdv);
|
|
visit_type_%(c_name)s(v, &obj, NULL, NULL);
|
|
qapi_dealloc_visitor_cleanup(qdv);
|
|
}
|
|
''',
|
|
c_name=c_name(name))
|
|
return ret
|
|
|
|
|
|
class QAPISchemaGenTypeVisitor(QAPISchemaVisitor):
|
|
def __init__(self):
|
|
self.decl = None
|
|
self.defn = None
|
|
self._fwdecl = None
|
|
self._fwdefn = None
|
|
self._btin = None
|
|
|
|
def visit_begin(self, schema):
|
|
self.decl = ''
|
|
self.defn = ''
|
|
self._fwdecl = ''
|
|
self._fwdefn = ''
|
|
self._btin = guardstart('QAPI_TYPES_BUILTIN')
|
|
|
|
def visit_end(self):
|
|
self.decl = self._fwdecl + self.decl
|
|
self._fwdecl = None
|
|
self.defn = self._fwdefn + self.defn
|
|
self._fwdefn = None
|
|
# To avoid header dependency hell, we always generate
|
|
# declarations for built-in types in our header files and
|
|
# simply guard them. See also do_builtins (command line
|
|
# option -b).
|
|
self._btin += guardend('QAPI_TYPES_BUILTIN')
|
|
self.decl = self._btin + self.decl
|
|
self._btin = None
|
|
|
|
def visit_needed(self, entity):
|
|
# Visit everything except implicit objects
|
|
return not (entity.is_implicit() and
|
|
isinstance(entity, QAPISchemaObjectType))
|
|
|
|
def _gen_type_cleanup(self, name):
|
|
self.decl += gen_type_cleanup_decl(name)
|
|
self.defn += gen_type_cleanup(name)
|
|
|
|
def visit_enum_type(self, name, info, values, prefix):
|
|
# Special case for our lone builtin enum type
|
|
# TODO use something cleaner than existence of info
|
|
if not info:
|
|
self._btin += gen_enum(name, values, prefix)
|
|
if do_builtins:
|
|
self.defn += gen_enum_lookup(name, values, prefix)
|
|
else:
|
|
self._fwdecl += gen_enum(name, values, prefix)
|
|
self._fwdefn += gen_enum_lookup(name, values, prefix)
|
|
|
|
def visit_array_type(self, name, info, element_type):
|
|
if isinstance(element_type, QAPISchemaBuiltinType):
|
|
self._btin += gen_fwd_object_or_array(name)
|
|
self._btin += gen_array(name, element_type)
|
|
self._btin += gen_type_cleanup_decl(name)
|
|
if do_builtins:
|
|
self.defn += gen_type_cleanup(name)
|
|
else:
|
|
self._fwdecl += gen_fwd_object_or_array(name)
|
|
self.decl += gen_array(name, element_type)
|
|
self._gen_type_cleanup(name)
|
|
|
|
def visit_object_type(self, name, info, base, members, variants):
|
|
self._fwdecl += gen_fwd_object_or_array(name)
|
|
self.decl += gen_object(name, base, members, variants)
|
|
if base:
|
|
self.decl += gen_upcast(name, base)
|
|
self._gen_type_cleanup(name)
|
|
|
|
def visit_alternate_type(self, name, info, variants):
|
|
self._fwdecl += gen_fwd_object_or_array(name)
|
|
self.decl += gen_object(name, None, [variants.tag_member], variants)
|
|
self._gen_type_cleanup(name)
|
|
|
|
# If you link code generated from multiple schemata, you want only one
|
|
# instance of the code for built-in types. Generate it only when
|
|
# do_builtins, enabled by command line option -b. See also
|
|
# QAPISchemaGenTypeVisitor.visit_end().
|
|
do_builtins = False
|
|
|
|
(input_file, output_dir, do_c, do_h, prefix, opts) = \
|
|
parse_command_line("b", ["builtins"])
|
|
|
|
for o, a in opts:
|
|
if o in ("-b", "--builtins"):
|
|
do_builtins = True
|
|
|
|
c_comment = '''
|
|
/*
|
|
* deallocation functions for schema-defined QAPI types
|
|
*
|
|
* Copyright IBM, Corp. 2011
|
|
*
|
|
* Authors:
|
|
* Anthony Liguori <aliguori@us.ibm.com>
|
|
* Michael Roth <mdroth@linux.vnet.ibm.com>
|
|
*
|
|
* This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
|
|
* See the COPYING.LIB file in the top-level directory.
|
|
*
|
|
*/
|
|
'''
|
|
h_comment = '''
|
|
/*
|
|
* schema-defined QAPI types
|
|
*
|
|
* Copyright IBM, Corp. 2011
|
|
*
|
|
* Authors:
|
|
* Anthony Liguori <aliguori@us.ibm.com>
|
|
*
|
|
* This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
|
|
* See the COPYING.LIB file in the top-level directory.
|
|
*
|
|
*/
|
|
'''
|
|
|
|
(fdef, fdecl) = open_output(output_dir, do_c, do_h, prefix,
|
|
'qapi-types.c', 'qapi-types.h',
|
|
c_comment, h_comment)
|
|
|
|
fdef.write(mcgen('''
|
|
#include "qapi/dealloc-visitor.h"
|
|
#include "%(prefix)sqapi-types.h"
|
|
#include "%(prefix)sqapi-visit.h"
|
|
''',
|
|
prefix=prefix))
|
|
|
|
# To avoid circular headers, use only typedefs.h here, not qobject.h
|
|
fdecl.write(mcgen('''
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include "qemu/typedefs.h"
|
|
'''))
|
|
|
|
schema = QAPISchema(input_file)
|
|
gen = QAPISchemaGenTypeVisitor()
|
|
schema.visit(gen)
|
|
fdef.write(gen.defn)
|
|
fdecl.write(gen.decl)
|
|
|
|
close_output(fdef, fdecl)
|