Merge remote-tracking branch 'qmp/queue/qmp' into staging

* qmp/queue/qmp:
  qmp: document strict parsing
  qmp: parse commands in strict mode
  qmp: add and use q type specifier
  qapi: add strict mode to input visitor
  qapi: place outermost object on qiv stack
  qapi: untangle next_list
  qapi: allow freeing partially-allocated objects
  qapi: shortcut visits on errors
  qapi: fix memory leak on error
  qapi: fail hard on stack imbalance
  qapi: add a test case for type errors
  qapi: add struct-errors test case to test-qmp-output-visitor
  qapi: fix double free in qmp_output_visitor_cleanup()
This commit is contained in:
Anthony Liguori 2012-03-28 12:45:22 -05:00
commit c76d1a9b3f
14 changed files with 406 additions and 55 deletions

View file

@ -209,13 +209,27 @@ incompatible way are disabled by default and will be advertised by the
capabilities array (section '2.2 Server Greeting'). Thus, Clients can check
that array and enable the capabilities they support.
Additionally, Clients must not assume any particular:
The QMP Server performs a type check on the arguments to a command. It
generates an error if a value does not have the expected type for its
key, or if it does not understand a key that the Client included. The
strictness of the Server catches wrong assumptions of Clients about
the Server's schema. Clients can assume that, when such validation
errors occur, they will be reported before the command generated any
side effect.
- Size of json-objects or length of json-arrays
However, Clients must not assume any particular:
- Length of json-arrays
- Size of json-objects; in particular, future versions of QEMU may add
new keys and Clients should be able to ignore them.
- Order of json-object members or json-array elements
- Amount of errors generated by a command, that is, new errors can be added
to any existing command in newer versions of the Server
Of course, the Server does guarantee to send valid JSON. But apart from
this, a Client should be "conservative in what they send, and liberal in
what they accept".
6. Downstream extension of QMP
------------------------------

View file

@ -194,11 +194,11 @@ Example:
void visit_type_UserDefOneList(Visitor *m, UserDefOneList ** obj, const char *name, Error **errp)
{
GenericList *i;
GenericList *i, **prev = (GenericList **)obj;
visit_start_list(m, name, errp);
for (i = visit_next_list(m, (GenericList **)obj, errp); i; i = visit_next_list(m, &i, errp)) {
for (; (i = visit_next_list(m, prev, errp)) != NULL; prev = &i) {
UserDefOneList *native_i = (UserDefOneList *)i;
visit_type_UserDefOne(m, &native_i->value, NULL, errp);
}

View file

@ -4157,6 +4157,9 @@ static int check_client_args_type(const QDict *client_args,
case 'O':
assert(flags & QMP_ACCEPT_UNKNOWNS);
break;
case 'q':
/* Any QObject can be passed. */
break;
case '/':
case '.':
/*

View file

@ -8,7 +8,7 @@
# for testing nested structs
{ 'type': 'UserDefOne',
'data': { 'integer': 'int', 'string': 'str' } }
'data': { 'integer': 'int', 'string': 'str', '*enum1': 'EnumOne' } }
{ 'type': 'UserDefTwo',
'data': { 'string': 'str',

View file

@ -22,16 +22,17 @@
typedef struct StackObject
{
const QObject *obj;
const QListEntry *entry;
QObject *obj;
const QListEntry *entry;
GHashTable *h;
} StackObject;
struct QmpInputVisitor
{
Visitor visitor;
QObject *obj;
StackObject stack[QIV_STACK_SIZE];
int nb_stack;
bool strict;
};
static QmpInputVisitor *to_qiv(Visitor *v)
@ -39,21 +40,18 @@ static QmpInputVisitor *to_qiv(Visitor *v)
return container_of(v, QmpInputVisitor, visitor);
}
static const QObject *qmp_input_get_object(QmpInputVisitor *qiv,
const char *name)
static QObject *qmp_input_get_object(QmpInputVisitor *qiv,
const char *name)
{
const QObject *qobj;
if (qiv->nb_stack == 0) {
qobj = qiv->obj;
} else {
qobj = qiv->stack[qiv->nb_stack - 1].obj;
}
QObject *qobj = qiv->stack[qiv->nb_stack - 1].obj;
if (qobj) {
if (name && qobject_type(qobj) == QTYPE_QDICT) {
if (qiv->stack[qiv->nb_stack - 1].h) {
g_hash_table_remove(qiv->stack[qiv->nb_stack - 1].h, name);
}
return qdict_get(qobject_to_qdict(qobj), name);
} else if (qiv->nb_stack > 0 && qobject_type(qobj) == QTYPE_QLIST) {
} else if (qiv->stack[qiv->nb_stack - 1].entry) {
return qlist_entry_obj(qiv->stack[qiv->nb_stack - 1].entry);
}
}
@ -61,34 +59,57 @@ static const QObject *qmp_input_get_object(QmpInputVisitor *qiv,
return qobj;
}
static void qmp_input_push(QmpInputVisitor *qiv, const QObject *obj, Error **errp)
static void qdict_add_key(const char *key, QObject *obj, void *opaque)
{
qiv->stack[qiv->nb_stack].obj = obj;
if (qobject_type(obj) == QTYPE_QLIST) {
qiv->stack[qiv->nb_stack].entry = qlist_first(qobject_to_qlist(obj));
}
qiv->nb_stack++;
GHashTable *h = opaque;
g_hash_table_insert(h, (gpointer) key, NULL);
}
static void qmp_input_push(QmpInputVisitor *qiv, QObject *obj, Error **errp)
{
GHashTable *h;
if (qiv->nb_stack >= QIV_STACK_SIZE) {
error_set(errp, QERR_BUFFER_OVERRUN);
return;
}
qiv->stack[qiv->nb_stack].obj = obj;
qiv->stack[qiv->nb_stack].entry = NULL;
qiv->stack[qiv->nb_stack].h = NULL;
if (qiv->strict && qobject_type(obj) == QTYPE_QDICT) {
h = g_hash_table_new(g_str_hash, g_str_equal);
qdict_iter(qobject_to_qdict(obj), qdict_add_key, h);
qiv->stack[qiv->nb_stack].h = h;
}
qiv->nb_stack++;
}
static void qmp_input_pop(QmpInputVisitor *qiv, Error **errp)
{
qiv->nb_stack--;
if (qiv->nb_stack < 0) {
error_set(errp, QERR_BUFFER_OVERRUN);
return;
GHashTableIter iter;
gpointer key;
if (qiv->strict && qiv->stack[qiv->nb_stack - 1].h) {
g_hash_table_iter_init(&iter, qiv->stack[qiv->nb_stack - 1].h);
if (g_hash_table_iter_next(&iter, &key, NULL)) {
error_set(errp, QERR_QMP_EXTRA_MEMBER, (char *) key);
}
g_hash_table_unref(qiv->stack[qiv->nb_stack - 1].h);
}
assert(qiv->nb_stack > 0);
qiv->nb_stack--;
}
static void qmp_input_start_struct(Visitor *v, void **obj, const char *kind,
const char *name, size_t size, Error **errp)
{
QmpInputVisitor *qiv = to_qiv(v);
const QObject *qobj = qmp_input_get_object(qiv, name);
QObject *qobj = qmp_input_get_object(qiv, name);
Error *err = NULL;
if (!qobj || qobject_type(qobj) != QTYPE_QDICT) {
error_set(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
@ -96,8 +117,9 @@ static void qmp_input_start_struct(Visitor *v, void **obj, const char *kind,
return;
}
qmp_input_push(qiv, qobj, errp);
if (error_is_set(errp)) {
qmp_input_push(qiv, qobj, &err);
if (err) {
error_propagate(errp, err);
return;
}
@ -116,7 +138,7 @@ static void qmp_input_end_struct(Visitor *v, Error **errp)
static void qmp_input_start_list(Visitor *v, const char *name, Error **errp)
{
QmpInputVisitor *qiv = to_qiv(v);
const QObject *qobj = qmp_input_get_object(qiv, name);
QObject *qobj = qmp_input_get_object(qiv, name);
if (!qobj || qobject_type(qobj) != QTYPE_QLIST) {
error_set(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
@ -133,18 +155,24 @@ static GenericList *qmp_input_next_list(Visitor *v, GenericList **list,
QmpInputVisitor *qiv = to_qiv(v);
GenericList *entry;
StackObject *so = &qiv->stack[qiv->nb_stack - 1];
bool first;
if (so->entry == NULL) {
so->entry = qlist_first(qobject_to_qlist(so->obj));
first = true;
} else {
so->entry = qlist_next(so->entry);
first = false;
}
if (so->entry == NULL) {
return NULL;
}
entry = g_malloc0(sizeof(*entry));
if (*list) {
so->entry = qlist_next(so->entry);
if (so->entry == NULL) {
g_free(entry);
return NULL;
}
if (first) {
*list = entry;
} else {
(*list)->next = entry;
}
@ -162,7 +190,7 @@ static void qmp_input_type_int(Visitor *v, int64_t *obj, const char *name,
Error **errp)
{
QmpInputVisitor *qiv = to_qiv(v);
const QObject *qobj = qmp_input_get_object(qiv, name);
QObject *qobj = qmp_input_get_object(qiv, name);
if (!qobj || qobject_type(qobj) != QTYPE_QINT) {
error_set(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
@ -177,7 +205,7 @@ static void qmp_input_type_bool(Visitor *v, bool *obj, const char *name,
Error **errp)
{
QmpInputVisitor *qiv = to_qiv(v);
const QObject *qobj = qmp_input_get_object(qiv, name);
QObject *qobj = qmp_input_get_object(qiv, name);
if (!qobj || qobject_type(qobj) != QTYPE_QBOOL) {
error_set(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
@ -192,7 +220,7 @@ static void qmp_input_type_str(Visitor *v, char **obj, const char *name,
Error **errp)
{
QmpInputVisitor *qiv = to_qiv(v);
const QObject *qobj = qmp_input_get_object(qiv, name);
QObject *qobj = qmp_input_get_object(qiv, name);
if (!qobj || qobject_type(qobj) != QTYPE_QSTRING) {
error_set(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
@ -207,7 +235,7 @@ static void qmp_input_type_number(Visitor *v, double *obj, const char *name,
Error **errp)
{
QmpInputVisitor *qiv = to_qiv(v);
const QObject *qobj = qmp_input_get_object(qiv, name);
QObject *qobj = qmp_input_get_object(qiv, name);
if (!qobj || qobject_type(qobj) != QTYPE_QFLOAT) {
error_set(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
@ -222,7 +250,7 @@ static void qmp_input_start_optional(Visitor *v, bool *present,
const char *name, Error **errp)
{
QmpInputVisitor *qiv = to_qiv(v);
const QObject *qobj = qmp_input_get_object(qiv, name);
QObject *qobj = qmp_input_get_object(qiv, name);
if (!qobj) {
*present = false;
@ -239,7 +267,7 @@ Visitor *qmp_input_get_visitor(QmpInputVisitor *v)
void qmp_input_visitor_cleanup(QmpInputVisitor *v)
{
qobject_decref(v->obj);
qobject_decref(v->stack[0].obj);
g_free(v);
}
@ -261,8 +289,18 @@ QmpInputVisitor *qmp_input_visitor_new(QObject *obj)
v->visitor.type_number = qmp_input_type_number;
v->visitor.start_optional = qmp_input_start_optional;
v->obj = obj;
qobject_incref(v->obj);
qmp_input_push(v, obj, NULL);
qobject_incref(obj);
return v;
}
QmpInputVisitor *qmp_input_visitor_new_strict(QObject *obj)
{
QmpInputVisitor *v;
v = qmp_input_visitor_new(obj);
v->strict = true;
return v;
}

View file

@ -20,6 +20,8 @@
typedef struct QmpInputVisitor QmpInputVisitor;
QmpInputVisitor *qmp_input_visitor_new(QObject *obj);
QmpInputVisitor *qmp_input_visitor_new_strict(QObject *obj);
void qmp_input_visitor_cleanup(QmpInputVisitor *v);
Visitor *qmp_input_get_visitor(QmpInputVisitor *v);

View file

@ -199,14 +199,16 @@ void qmp_output_visitor_cleanup(QmpOutputVisitor *v)
{
QStackEntry *e, *tmp;
/* The bottom QStackEntry, if any, owns the root QObject. See the
* qmp_output_push_obj() invocations in qmp_output_add_obj(). */
QObject *root = QTAILQ_EMPTY(&v->stack) ? NULL : qmp_output_first(v);
QTAILQ_FOREACH_SAFE(e, &v->stack, node, tmp) {
QTAILQ_REMOVE(&v->stack, e, node);
if (e->value) {
qobject_decref(e->value);
}
g_free(e);
}
qobject_decref(root);
g_free(v);
}

View file

@ -708,7 +708,7 @@ EQMP
},
{
.name = "transaction",
.args_type = "actions:O",
.args_type = "actions:q",
.mhandler.cmd_new = qmp_marshal_input_transaction,
},
@ -2125,7 +2125,7 @@ EQMP
{
.name = "qom-set",
.args_type = "path:s,property:s,opts:O",
.args_type = "path:s,property:s,value:q",
.mhandler.cmd_new = qmp_qom_set,
},

View file

@ -140,7 +140,7 @@ v = qapi_dealloc_get_visitor(md);
''')
else:
ret += mcgen('''
mi = qmp_input_visitor_new(%(obj)s);
mi = qmp_input_visitor_new_strict(%(obj)s);
v = qmp_input_get_visitor(mi);
''',
obj=obj)

View file

@ -61,7 +61,13 @@ def generate_visit_struct(name, members):
void visit_type_%(name)s(Visitor *m, %(name)s ** obj, const char *name, Error **errp)
{
if (error_is_set(errp)) {
return;
}
visit_start_struct(m, (void **)obj, "%(name)s", name, sizeof(%(name)s), errp);
if (obj && !*obj) {
goto end;
}
''',
name=name)
push_indent()
@ -69,6 +75,7 @@ void visit_type_%(name)s(Visitor *m, %(name)s ** obj, const char *name, Error **
pop_indent()
ret += mcgen('''
end:
visit_end_struct(m, errp);
}
''')
@ -79,11 +86,14 @@ def generate_visit_list(name, members):
void visit_type_%(name)sList(Visitor *m, %(name)sList ** obj, const char *name, Error **errp)
{
GenericList *i, **head = (GenericList **)obj;
GenericList *i, **prev = (GenericList **)obj;
if (error_is_set(errp)) {
return;
}
visit_start_list(m, name, errp);
for (*head = i = visit_next_list(m, head, errp); i; i = visit_next_list(m, &i, errp)) {
for (; (i = visit_next_list(m, prev, errp)) != NULL; prev = &i) {
%(name)sList *native_i = (%(name)sList *)i;
visit_type_%(name)s(m, &native_i->value, NULL, errp);
}
@ -112,7 +122,13 @@ void visit_type_%(name)s(Visitor *m, %(name)s ** obj, const char *name, Error **
{
Error *err = NULL;
if (error_is_set(errp)) {
return;
}
visit_start_struct(m, (void **)obj, "%(name)s", name, sizeof(%(name)s), &err);
if (obj && !*obj) {
goto end;
}
visit_type_%(name)sKind(m, &(*obj)->kind, "type", &err);
if (err) {
error_propagate(errp, err);

234
test-qmp-input-strict.c Normal file
View file

@ -0,0 +1,234 @@
/*
* QMP Input Visitor unit-tests (strict mode).
*
* Copyright (C) 2011-2012 Red Hat Inc.
*
* Authors:
* Luiz Capitulino <lcapitulino@redhat.com>
* Paolo Bonzini <pbonzini@redhat.com>
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/
#include <glib.h>
#include <stdarg.h>
#include "qapi/qmp-input-visitor.h"
#include "test-qapi-types.h"
#include "test-qapi-visit.h"
#include "qemu-objects.h"
typedef struct TestInputVisitorData {
QObject *obj;
QmpInputVisitor *qiv;
} TestInputVisitorData;
static void validate_teardown(TestInputVisitorData *data,
const void *unused)
{
qobject_decref(data->obj);
data->obj = NULL;
if (data->qiv) {
qmp_input_visitor_cleanup(data->qiv);
data->qiv = NULL;
}
}
/* This is provided instead of a test setup function so that the JSON
string used by the tests are kept in the test functions (and not
int main()) */
static GCC_FMT_ATTR(2, 3)
Visitor *validate_test_init(TestInputVisitorData *data,
const char *json_string, ...)
{
Visitor *v;
va_list ap;
va_start(ap, json_string);
data->obj = qobject_from_jsonv(json_string, &ap);
va_end(ap);
g_assert(data->obj != NULL);
data->qiv = qmp_input_visitor_new_strict(data->obj);
g_assert(data->qiv != NULL);
v = qmp_input_get_visitor(data->qiv);
g_assert(v != NULL);
return v;
}
typedef struct TestStruct
{
int64_t integer;
bool boolean;
char *string;
} TestStruct;
static void visit_type_TestStruct(Visitor *v, TestStruct **obj,
const char *name, Error **errp)
{
visit_start_struct(v, (void **)obj, "TestStruct", name, sizeof(TestStruct),
errp);
visit_type_int(v, &(*obj)->integer, "integer", errp);
visit_type_bool(v, &(*obj)->boolean, "boolean", errp);
visit_type_str(v, &(*obj)->string, "string", errp);
visit_end_struct(v, errp);
}
static void test_validate_struct(TestInputVisitorData *data,
const void *unused)
{
TestStruct *p = NULL;
Error *errp = NULL;
Visitor *v;
v = validate_test_init(data, "{ 'integer': -42, 'boolean': true, 'string': 'foo' }");
visit_type_TestStruct(v, &p, NULL, &errp);
g_assert(!error_is_set(&errp));
g_free(p->string);
g_free(p);
}
static void test_validate_struct_nested(TestInputVisitorData *data,
const void *unused)
{
UserDefNested *udp = NULL;
Error *errp = NULL;
Visitor *v;
v = validate_test_init(data, "{ 'string0': 'string0', 'dict1': { 'string1': 'string1', 'dict2': { 'userdef1': { 'integer': 42, 'string': 'string' }, 'string2': 'string2'}}}");
visit_type_UserDefNested(v, &udp, NULL, &errp);
g_assert(!error_is_set(&errp));
qapi_free_UserDefNested(udp);
}
static void test_validate_list(TestInputVisitorData *data,
const void *unused)
{
UserDefOneList *head = NULL;
Error *errp = NULL;
Visitor *v;
v = validate_test_init(data, "[ { 'string': 'string0', 'integer': 42 }, { 'string': 'string1', 'integer': 43 }, { 'string': 'string2', 'integer': 44 } ]");
visit_type_UserDefOneList(v, &head, NULL, &errp);
g_assert(!error_is_set(&errp));
qapi_free_UserDefOneList(head);
}
static void test_validate_union(TestInputVisitorData *data,
const void *unused)
{
UserDefUnion *tmp = NULL;
Visitor *v;
Error *errp = NULL;
v = validate_test_init(data, "{ 'type': 'b', 'data' : { 'integer': 42 } }");
visit_type_UserDefUnion(v, &tmp, NULL, &errp);
g_assert(!error_is_set(&errp));
qapi_free_UserDefUnion(tmp);
}
static void test_validate_fail_struct(TestInputVisitorData *data,
const void *unused)
{
TestStruct *p = NULL;
Error *errp = NULL;
Visitor *v;
v = validate_test_init(data, "{ 'integer': -42, 'boolean': true, 'string': 'foo', 'extra': 42 }");
visit_type_TestStruct(v, &p, NULL, &errp);
g_assert(error_is_set(&errp));
if (p) {
g_free(p->string);
}
g_free(p);
}
static void test_validate_fail_struct_nested(TestInputVisitorData *data,
const void *unused)
{
UserDefNested *udp = NULL;
Error *errp = NULL;
Visitor *v;
v = validate_test_init(data, "{ 'string0': 'string0', 'dict1': { 'string1': 'string1', 'dict2': { 'userdef1': { 'integer': 42, 'string': 'string', 'extra': [42, 23, {'foo':'bar'}] }, 'string2': 'string2'}}}");
visit_type_UserDefNested(v, &udp, NULL, &errp);
g_assert(error_is_set(&errp));
qapi_free_UserDefNested(udp);
}
static void test_validate_fail_list(TestInputVisitorData *data,
const void *unused)
{
UserDefOneList *head = NULL;
Error *errp = NULL;
Visitor *v;
v = validate_test_init(data, "[ { 'string': 'string0', 'integer': 42 }, { 'string': 'string1', 'integer': 43 }, { 'string': 'string2', 'integer': 44, 'extra': 'ggg' } ]");
visit_type_UserDefOneList(v, &head, NULL, &errp);
g_assert(error_is_set(&errp));
qapi_free_UserDefOneList(head);
}
static void test_validate_fail_union(TestInputVisitorData *data,
const void *unused)
{
UserDefUnion *tmp = NULL;
Error *errp = NULL;
Visitor *v;
v = validate_test_init(data, "{ 'type': 'b', 'data' : { 'integer': 42 }, 'extra': 'yyy' }");
visit_type_UserDefUnion(v, &tmp, NULL, &errp);
g_assert(error_is_set(&errp));
qapi_free_UserDefUnion(tmp);
}
static void validate_test_add(const char *testpath,
TestInputVisitorData *data,
void (*test_func)(TestInputVisitorData *data, const void *user_data))
{
g_test_add(testpath, TestInputVisitorData, data, NULL, test_func,
validate_teardown);
}
int main(int argc, char **argv)
{
TestInputVisitorData testdata;
g_test_init(&argc, &argv, NULL);
validate_test_add("/visitor/input-strict/pass/struct",
&testdata, test_validate_struct);
validate_test_add("/visitor/input-strict/pass/struct-nested",
&testdata, test_validate_struct_nested);
validate_test_add("/visitor/input-strict/pass/list",
&testdata, test_validate_list);
validate_test_add("/visitor/input-strict/pass/union",
&testdata, test_validate_union);
validate_test_add("/visitor/input-strict/fail/struct",
&testdata, test_validate_fail_struct);
validate_test_add("/visitor/input-strict/fail/struct-nested",
&testdata, test_validate_fail_struct_nested);
validate_test_add("/visitor/input-strict/fail/list",
&testdata, test_validate_fail_list);
validate_test_add("/visitor/input-strict/fail/union",
&testdata, test_validate_fail_union);
g_test_run();
return 0;
}

View file

@ -258,6 +258,23 @@ static void input_visitor_test_add(const char *testpath,
visitor_input_teardown);
}
static void test_visitor_in_errors(TestInputVisitorData *data,
const void *unused)
{
TestStruct *p = NULL;
Error *errp = NULL;
Visitor *v;
v = visitor_input_test_init(data, "{ 'integer': false, 'boolean': 'foo', 'string': -42 }");
visit_type_TestStruct(v, &p, NULL, &errp);
g_assert(error_is_set(&errp));
g_assert(p->string == NULL);
g_free(p->string);
g_free(p);
}
int main(int argc, char **argv)
{
TestInputVisitorData in_visitor_data;
@ -282,6 +299,8 @@ int main(int argc, char **argv)
&in_visitor_data, test_visitor_in_list);
input_visitor_test_add("/visitor/input/union",
&in_visitor_data, test_visitor_in_union);
input_visitor_test_add("/visitor/input/errors",
&in_visitor_data, test_visitor_in_errors);
g_test_run();

View file

@ -274,6 +274,24 @@ static void test_visitor_out_struct_nested(TestOutputVisitorData *data,
qapi_free_UserDefNested(ud2);
}
static void test_visitor_out_struct_errors(TestOutputVisitorData *data,
const void *unused)
{
EnumOne bad_values[] = { ENUM_ONE_MAX, -1 };
UserDefOne u = { 0 }, *pu = &u;
Error *errp;
int i;
for (i = 0; i < ARRAY_SIZE(bad_values) ; i++) {
errp = NULL;
u.has_enum1 = true;
u.enum1 = bad_values[i];
visit_type_UserDefOne(data->ov, &pu, "unused", &errp);
g_assert(error_is_set(&errp) == true);
error_free(errp);
}
}
typedef struct TestStructList
{
TestStruct *value;
@ -444,6 +462,8 @@ int main(int argc, char **argv)
&out_visitor_data, test_visitor_out_struct);
output_visitor_test_add("/visitor/output/struct-nested",
&out_visitor_data, test_visitor_out_struct_nested);
output_visitor_test_add("/visitor/output/struct-errors",
&out_visitor_data, test_visitor_out_struct_errors);
output_visitor_test_add("/visitor/output/list",
&out_visitor_data, test_visitor_out_list);
output_visitor_test_add("/visitor/output/list-qapi-free",

View file

@ -16,7 +16,7 @@ check-qfloat: check-qfloat.o qfloat.o $(tools-obj-y)
check-qjson: check-qjson.o $(qobject-obj-y) $(tools-obj-y)
test-coroutine: test-coroutine.o qemu-timer-common.o async.o $(coroutine-obj-y) $(tools-obj-y)
test-qmp-input-visitor.o test-qmp-output-visitor.o \
test-qmp-input-visitor.o test-qmp-output-visitor.o test-qmp-input-strict.o \
test-string-input-visitor.o test-string-output-visitor.o \
test-qmp-commands.o: QEMU_CFLAGS += -I $(qapi-dir)
@ -37,6 +37,9 @@ test-string-output-visitor: test-string-output-visitor.o $(qobject-obj-y) $(qapi
test-string-input-visitor.o: $(addprefix $(qapi-dir)/, test-qapi-types.c test-qapi-types.h test-qapi-visit.c test-qapi-visit.h) $(qapi-obj-y)
test-string-input-visitor: test-string-input-visitor.o $(qobject-obj-y) $(qapi-obj-y) $(tools-obj-y) $(qapi-dir)/test-qapi-visit.o $(qapi-dir)/test-qapi-types.o
test-qmp-input-strict.o: $(addprefix $(qapi-dir)/, test-qapi-types.c test-qapi-types.h test-qapi-visit.c test-qapi-visit.h) $(qapi-obj-y)
test-qmp-input-strict: test-qmp-input-strict.o $(qobject-obj-y) $(qapi-obj-y) $(tools-obj-y) $(qapi-dir)/test-qapi-visit.o $(qapi-dir)/test-qapi-types.o
test-qmp-output-visitor.o: $(addprefix $(qapi-dir)/, test-qapi-types.c test-qapi-types.h test-qapi-visit.c test-qapi-visit.h) $(qapi-obj-y)
test-qmp-output-visitor: test-qmp-output-visitor.o $(qobject-obj-y) $(qapi-obj-y) $(tools-obj-y) $(qapi-dir)/test-qapi-visit.o $(qapi-dir)/test-qapi-types.o