unicorn/qemu/scripts/qapi-visit.py
Eric Blake b978871f20
qapi: Don't cast Enum* to int*
C compilers are allowed to represent enums as a smaller type
than int, if all enum values fit in the smaller type. There
are even compiler flags that force the use of this smaller
representation, although using them changes the ABI of a
binary. Therefore, our generated code for visit_type_ENUM()
(for all qapi enums) was wrong for casting Enum* to int* when
calling visit_type_enum().

It appears that no one has been using compiler ABI switches
for qemu, because if they had, we are potentially dereferencing
beyond bounds or even risking a SIGBUS on platforms where
unaligned pointer dereferencing is fatal. But it is still
better to avoid the practice entirely, and just use the correct
types.

This matches the fix for alternate qapi types, done earlier in
commit 0426d53 "qapi: Simplify visiting of alternate types",
with generated code changing as:

| void visit_type_QType(Visitor *v, QType *obj, const char *name, Error **errp)
| {
|- visit_type_enum(v, (int *)obj, QType_lookup, "QType", name, errp);
|+ int value = *obj;
|+ visit_type_enum(v, &value, QType_lookup, "QType", name, errp);
|+ *obj = value;
| }

Backports commit 395a233f7c089f23e3c0d43ce34c709dc5acd7de from qemu
2018-02-19 22:24:19 -05:00

461 lines
12 KiB
Python

#
# QAPI visitor generator
#
# Copyright IBM, Corp. 2011
# Copyright (C) 2014-2016 Red Hat, Inc.
#
# Authors:
# Anthony Liguori <aliguori@us.ibm.com>
# Michael Roth <mdroth@linux.vnet.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 *
import re
# visit_type_FOO_implicit() is emitted as needed; track if it has already
# been output.
implicit_structs_seen = set()
# visit_type_FOO_fields() is always emitted; track if a forward declaration
# or implementation has already been output.
struct_fields_seen = set()
def gen_visit_decl(name, scalar=False):
c_type = c_name(name) + ' *'
if not scalar:
c_type += '*'
return mcgen('''
void visit_type_%(c_name)s(Visitor *v, %(c_type)sobj, const char *name, Error **errp);
''',
c_name=c_name(name), c_type=c_type)
def gen_visit_fields_decl(typ):
ret = ''
if typ.name not in struct_fields_seen:
ret += mcgen('''
static void visit_type_%(c_type)s_fields(Visitor *v, %(c_type)s **obj, Error **errp);
''',
c_type=typ.c_name())
struct_fields_seen.add(typ.name)
return ret
def gen_visit_implicit_struct(typ):
if typ in implicit_structs_seen:
return ''
implicit_structs_seen.add(typ)
ret = gen_visit_fields_decl(typ)
ret += mcgen('''
static void visit_type_implicit_%(c_type)s(Visitor *v, %(c_type)s **obj, Error **errp)
{
Error *err = NULL;
visit_start_implicit_struct(v, (void **)obj, sizeof(%(c_type)s), &err);
if (!err) {
visit_type_%(c_type)s_fields(v, obj, errp);
visit_end_implicit_struct(v);
}
error_propagate(errp, err);
}
''',
c_type=typ.c_name())
return ret
def gen_visit_struct_fields(name, base, members):
ret = ''
if base:
ret += gen_visit_fields_decl(base)
struct_fields_seen.add(name)
ret += mcgen('''
static void visit_type_%(c_name)s_fields(Visitor *v, %(c_name)s **obj, Error **errp)
{
Error *err = NULL;
''',
c_name=c_name(name))
if base:
ret += mcgen('''
visit_type_%(c_type)s_fields(v, (%(c_type)s **)obj, &err);
''',
c_type=base.c_name())
ret += gen_err_check()
ret += gen_visit_fields(members, prefix='(*obj)->')
# 'goto out' produced for base, and by gen_visit_fields() for each member
if base or members:
ret += mcgen('''
out:
''')
ret += mcgen('''
error_propagate(errp, err);
}
''')
return ret
def gen_visit_struct(name, base, members):
ret = gen_visit_struct_fields(name, base, members)
# FIXME: if *obj is NULL on entry, and visit_start_struct() assigns to
# *obj, but then visit_type_FOO_fields() fails, we should clean up *obj
# rather than leaving it non-NULL. As currently written, the caller must
# call qapi_free_FOO() to avoid a memory leak of the partial FOO.
ret += mcgen('''
void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error **errp)
{
Error *err = NULL;
visit_start_struct(v, (void **)obj, "%(name)s", name, sizeof(%(c_name)s), &err);
if (err) {
goto out;
}
if (!*obj) {
goto out_obj;
}
visit_type_%(c_name)s_fields(v, obj, &err);
error_propagate(errp, err);
err = NULL;
out_obj:
visit_end_struct(v, &err);
out:
error_propagate(errp, err);
}
''',
name=name, c_name=c_name(name))
return ret
def gen_visit_list(name, element_type):
# FIXME: if *obj is NULL on entry, and the first visit_next_list()
# assigns to *obj, while a later one fails, we should clean up *obj
# rather than leaving it non-NULL. As currently written, the caller must
# call qapi_free_FOOList() to avoid a memory leak of the partial FOOList.
return mcgen('''
void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error **errp)
{
Error *err = NULL;
GenericList *i, **prev;
visit_start_list(v, name, &err);
if (err) {
goto out;
}
for (prev = (GenericList **)obj;
!err && (i = visit_next_list(v, prev)) != NULL;
prev = &i) {
%(c_name)s *native_i = (%(c_name)s *)i;
visit_type_%(c_elt_type)s(v, &native_i->value, NULL, &err);
}
error_propagate(errp, err);
err = NULL;
visit_end_list(v);
out:
error_propagate(errp, err);
}
''',
c_name=c_name(name), c_elt_type=element_type.c_name())
def gen_visit_enum(name):
return mcgen('''
void visit_type_%(c_name)s(Visitor *v, %(c_name)s *obj, const char *name, Error **errp)
{
int value = *obj;
visit_type_enum(v, &value, %(c_name)s_lookup, "%(name)s", name, errp);
*obj = value;
}
''',
c_name=c_name(name), name=name)
def gen_visit_alternate(name, variants):
promote_int = 'true'
for var in variants.variants:
if var.type.alternate_qtype() == 'QTYPE_QINT':
promote_int = 'false'
ret = mcgen('''
void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error **errp)
{
Error *err = NULL;
visit_start_implicit_struct(v, (void**) obj, sizeof(%(c_name)s), &err);
if (err) {
goto out;
}
visit_get_next_type(v, &(*obj)->type, %(promote_int)s, name, &err);
if (err) {
goto out_obj;
}
switch ((*obj)->type) {
''',
c_name=c_name(name), promote_int=promote_int)
for var in variants.variants:
ret += mcgen('''
case %(case)s:
visit_type_%(c_type)s(v, &(*obj)->u.%(c_name)s, name, &err);
break;
''',
case=var.type.alternate_qtype(),
c_type=var.type.c_name(),
c_name=c_name(var.name))
ret += mcgen('''
default:
error_setg(&err, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
"%(name)s");
}
out_obj:
error_propagate(errp, err);
err = NULL;
visit_end_implicit_struct(v);
out:
error_propagate(errp, err);
}
''',
name=name)
return ret
def gen_visit_union(name, base, variants):
ret = ''
if base:
ret += gen_visit_fields_decl(base)
for var in variants.variants:
# Ugly special case for simple union TODO get rid of it
if not var.simple_union_type():
ret += gen_visit_implicit_struct(var.type)
ret += mcgen('''
void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error **errp)
{
Error *err = NULL;
visit_start_struct(v, (void **)obj, "%(name)s", name, sizeof(%(c_name)s), &err);
if (err) {
goto out;
}
if (!*obj) {
goto out_obj;
}
''',
c_name=c_name(name), name=name)
if base:
ret += mcgen('''
visit_type_%(c_name)s_fields(v, (%(c_name)s **)obj, &err);
''',
c_name=base.c_name())
else:
ret += mcgen('''
visit_type_%(c_type)s(v, &(*obj)->%(c_name)s, "%(name)s", &err);
''',
c_type=variants.tag_member.type.c_name(),
c_name=c_name(variants.tag_member.name),
name=variants.tag_member.name)
ret += gen_err_check(label='out_obj')
ret += mcgen('''
if (!visit_start_union(v, !!(*obj)->u.data, &err) || err) {
goto out_obj;
}
switch ((*obj)->%(c_name)s) {
''',
c_name=c_name(variants.tag_member.name))
for var in variants.variants:
# TODO ugly special case for simple union
simple_union_type = var.simple_union_type()
ret += mcgen('''
case %(case)s:
''',
case=c_enum_const(variants.tag_member.type.name,
var.name))
if simple_union_type:
ret += mcgen('''
visit_type_%(c_type)s(v, &(*obj)->u.%(c_name)s, "data", &err);
''',
c_type=simple_union_type.c_name(),
c_name=c_name(var.name))
else:
ret += mcgen('''
visit_type_implicit_%(c_type)s(v, &(*obj)->u.%(c_name)s, &err);
''',
c_type=var.type.c_name(),
c_name=c_name(var.name))
ret += mcgen('''
break;
''')
ret += mcgen('''
default:
abort();
}
out_obj:
error_propagate(errp, err);
err = NULL;
visit_end_struct(v, &err);
out:
error_propagate(errp, err);
}
''')
return ret
class QAPISchemaGenVisitVisitor(QAPISchemaVisitor):
def __init__(self):
self.decl = None
self.defn = None
self._btin = None
def visit_begin(self, schema):
self.decl = ''
self.defn = ''
self._btin = guardstart('QAPI_VISIT_BUILTIN')
def visit_end(self):
# 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_VISIT_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 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_visit_decl(name, scalar=True)
if do_builtins:
self.defn += gen_visit_enum(name)
else:
self.decl += gen_visit_decl(name, scalar=True)
self.defn += gen_visit_enum(name)
def visit_array_type(self, name, info, element_type):
decl = gen_visit_decl(name)
defn = gen_visit_list(name, element_type)
if isinstance(element_type, QAPISchemaBuiltinType):
self._btin += decl
if do_builtins:
self.defn += defn
else:
self.decl += decl
self.defn += defn
def visit_object_type(self, name, info, base, members, variants):
self.decl += gen_visit_decl(name)
if variants:
if members:
# Members other than variants.tag_member not implemented
assert len(members) == 1
assert members[0] == variants.tag_member
self.defn += gen_visit_union(name, base, variants)
else:
self.defn += gen_visit_struct(name, base, members)
def visit_alternate_type(self, name, info, variants):
self.decl += gen_visit_decl(name)
self.defn += gen_visit_alternate(name, variants)
# 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
# QAPISchemaGenVisitVisitor.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 = '''
/*
* schema-defined QAPI visitor functions
*
* 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.
*
*/
'''
h_comment = '''
/*
* schema-defined QAPI visitor functions
*
* 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-visit.c', 'qapi-visit.h',
c_comment, h_comment)
fdef.write(mcgen('''
#include "qemu-common.h"
#include "%(prefix)sqapi-visit.h"
''',
prefix=prefix))
fdecl.write(mcgen('''
#include "qapi/visitor.h"
#include "qapi/qmp/qerror.h"
#include "%(prefix)sqapi-types.h"
''',
prefix=prefix))
schema = QAPISchema(input_file)
gen = QAPISchemaGenVisitVisitor()
schema.visit(gen)
fdef.write(gen.defn)
fdecl.write(gen.decl)
close_output(fdef, fdecl)