diff options
author | Anthony Liguori <aliguori@us.ibm.com> | 2012-03-28 12:45:22 -0500 |
---|---|---|
committer | Anthony Liguori <aliguori@us.ibm.com> | 2012-03-28 12:45:22 -0500 |
commit | c76d1a9b3f367754966c326d81ff8566798b473a (patch) | |
tree | 2ac34dddcb214c3a7de2834f8f69e8211f697a5e | |
parent | 0a5a4e0568d4cc981457a2d1e7f86923eeff94ed (diff) | |
parent | 1829851cfee10e196abec50325d828de182fd356 (diff) |
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()
-rw-r--r-- | QMP/qmp-spec.txt | 20 | ||||
-rw-r--r-- | docs/qapi-code-gen.txt | 4 | ||||
-rw-r--r-- | monitor.c | 3 | ||||
-rw-r--r-- | qapi-schema-test.json | 2 | ||||
-rw-r--r-- | qapi/qmp-input-visitor.c | 120 | ||||
-rw-r--r-- | qapi/qmp-input-visitor.h | 2 | ||||
-rw-r--r-- | qapi/qmp-output-visitor.c | 8 | ||||
-rw-r--r-- | qmp-commands.hx | 4 | ||||
-rw-r--r-- | scripts/qapi-commands.py | 2 | ||||
-rw-r--r-- | scripts/qapi-visit.py | 20 | ||||
-rw-r--r-- | test-qmp-input-strict.c | 234 | ||||
-rw-r--r-- | test-qmp-input-visitor.c | 19 | ||||
-rw-r--r-- | test-qmp-output-visitor.c | 20 | ||||
-rw-r--r-- | tests/Makefile | 5 |
14 files changed, 407 insertions, 56 deletions
diff --git a/QMP/qmp-spec.txt b/QMP/qmp-spec.txt index 9d30a8ce6e..1ba916c9f2 100644 --- a/QMP/qmp-spec.txt +++ b/QMP/qmp-spec.txt @@ -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: - -- Size of json-objects or length of json-arrays +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. + +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 ------------------------------ diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt index 5831e371ea..ad11767a2f 100644 --- a/docs/qapi-code-gen.txt +++ b/docs/qapi-code-gen.txt @@ -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); } @@ -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 '.': /* diff --git a/qapi-schema-test.json b/qapi-schema-test.json index 8c7f9f79f4..9eae3501d7 100644 --- a/qapi-schema-test.json +++ b/qapi-schema-test.json @@ -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', diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c index e6b6152e08..74386b9b1b 100644 --- a/qapi/qmp-input-visitor.c +++ b/qapi/qmp-input-visitor.c @@ -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; } diff --git a/qapi/qmp-input-visitor.h b/qapi/qmp-input-visitor.h index 3f798f0335..e0a48a5f3b 100644 --- a/qapi/qmp-input-visitor.h +++ b/qapi/qmp-input-visitor.h @@ -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); diff --git a/qapi/qmp-output-visitor.c b/qapi/qmp-output-visitor.c index e0697b0d0f..2bce9d5db1 100644 --- a/qapi/qmp-output-visitor.c +++ b/qapi/qmp-output-visitor.c @@ -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); } diff --git a/qmp-commands.hx b/qmp-commands.hx index c626ba8d3d..944787161f 100644 --- a/qmp-commands.hx +++ b/qmp-commands.hx @@ -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, }, diff --git a/scripts/qapi-commands.py b/scripts/qapi-commands.py index 30a24d211b..0b4f0a0fe1 100644 --- a/scripts/qapi-commands.py +++ b/scripts/qapi-commands.py @@ -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) diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py index 78c947cd9c..8d4e94a45f 100644 --- a/scripts/qapi-visit.py +++ b/scripts/qapi-visit.py @@ -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); diff --git a/test-qmp-input-strict.c b/test-qmp-input-strict.c new file mode 100644 index 0000000000..f6df8cbe1e --- /dev/null +++ b/test-qmp-input-strict.c @@ -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; +} diff --git a/test-qmp-input-visitor.c b/test-qmp-input-visitor.c index 1996e49576..c30fdc4e59 100644 --- a/test-qmp-input-visitor.c +++ b/test-qmp-input-visitor.c @@ -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(); diff --git a/test-qmp-output-visitor.c b/test-qmp-output-visitor.c index 4d6c4d4420..24a6359504 100644 --- a/test-qmp-output-visitor.c +++ b/test-qmp-output-visitor.c @@ -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", diff --git a/tests/Makefile b/tests/Makefile index 94ea3421ad..2a2fff7c6d 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -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 |